/**
 * PANDA 3D SOFTWARE
 * Copyright (c) Carnegie Mellon University.  All rights reserved.
 *
 * All use of this software is subject to the terms of the revised BSD
 * license.  You should have received a copy of this license along
 * with this source code in a file named "LICENSE."
 *
 * @file pointerToArray_ext.I
 * @author rdb
 * @date 2015-02-08
 */

/**
 * This is a helper function to set most attributes of a Py_buffer in a manner
 * that accommodates square matrices (in accordance with PEP 3118). It is tested
 * for use with NumPy. The resulting array will be of shape
 * (num_matrices, size, size) where size is the number of matrix rows (=columns)
 */
INLINE void set_matrix_view(Py_buffer &view, int flags, int length, int size, bool double_prec, bool read_only) {
  int item_size, mat_size;
  const char *format;

  if (double_prec) {
    item_size = sizeof(double);
    format = get_format_code(double);
  } else {
    item_size = sizeof(float);
    format = get_format_code(float);
  }

  if (size == 3 && !double_prec) {
    mat_size = sizeof(LMatrix3f);
  } else if (size == 3 && double_prec) {
    mat_size = sizeof(LMatrix3d);
  } else if (size == 4 && !double_prec) {
    mat_size = sizeof(UnalignedLMatrix4f);
  } else if (size == 4 && double_prec) {
    mat_size = sizeof(UnalignedLMatrix4d);
  } else {
    nassertv_always(false);
    return; // Make sure compiler knows control flow doesn't proceed.
  }

  view.len = length * mat_size;
  view.readonly = (read_only ? 1 : 0);
  view.itemsize = item_size;
  view.format = nullptr;
  if ((flags & PyBUF_FORMAT) == PyBUF_FORMAT) {
    view.format = (char*) format;
  }
  view.ndim = 3;
  view.shape = nullptr;
  if ((flags & PyBUF_ND) == PyBUF_ND) {
    // This leaks, which sucks, but __releasebuffer__ doesn't give us the same
    // pointer, so we would need to store it elsewhere if we wanted to delete
    // it there.  Eh, it's just an int, who cares.
    Py_ssize_t* shape = new Py_ssize_t[3];
    shape[0] = length;
    shape[1] = size;
    shape[2] = size;
    view.shape = shape;
  }
  view.strides = nullptr;
  if ((flags & PyBUF_STRIDES) == PyBUF_STRIDES) {
    Py_ssize_t* strides = new Py_ssize_t[3];
    strides[0] = mat_size;
    strides[1] = item_size * size;
    strides[2] = item_size;
    view.strides = strides;
  }
  view.suboffsets = nullptr;
}

/**
 * This special constructor accepts a Python list of elements, or a Python
 * string (or a bytes object, in Python 3), or any object that supports the
 * Python buffer protocol.
 */
template<class Element>
INLINE void Extension<PointerToArray<Element> >::
__init__(PyObject *self, PyObject *source) {
  if (PyObject_CheckBuffer(source)) {
    // It's a byte sequence, or any object that exports the buffer protocol.
    this->set_data(source);
    return;
  }

  // Don't allow a unicode object even though it's a sequence.
  if (!PySequence_Check(source) || PyUnicode_CheckExact(source)) {
    // If passed with a non-sequence, this isn't the right constructor.
    PyErr_SetString(PyExc_TypeError,
                    "PointerToArray constructor requires a sequence or buffer object");
    return;
  }

  // Now construct the internal list by copying the elements one-at-a-time
  // from Python.
  PyObject *dict = Dtool_GetPyTypeObject(DtoolInstance_TYPE(self))->tp_dict;
  PyObject *push_back = PyDict_GetItemString(dict, "push_back");
  if (push_back == nullptr) {
    PyErr_BadArgument();
    return;
  }

  // We need to initialize the this pointer before we can call push_back.
  DtoolInstance_INIT_PTR(self, this->_this);

  Py_BEGIN_CRITICAL_SECTION(source);
  Py_ssize_t size = PySequence_Size(source);
  this->_this->reserve(size);
  for (Py_ssize_t i = 0; i < size; ++i) {
    PyObject *item = PySequence_GetItem(source, i);
    if (item == nullptr) {
      break;
    }
    PyObject *result = PyObject_CallFunctionObjArgs(push_back, self, item, nullptr);
    Py_DECREF(item);
    if (result == nullptr) {
      // Unable to add item--probably it wasn't of the appropriate type.
      PyErr_Print();
      PyErr_Format(PyExc_TypeError,
                   "Element %zd in sequence passed to PointerToArray "
                   "constructor could not be added", i);
      break;
    }
    Py_DECREF(result);
  }
  Py_END_CRITICAL_SECTION();
}

/**
 * Same as get_element(), this returns the nth element of the array.
 */
template<class Element>
INLINE const Element &Extension<PointerToArray<Element> >::
__getitem__(size_t n) const {
  return this->_this->get_element(n);
}

/**
 * Same as set_element(), this replaces the nth element of the array.
 */
template<class Element>
INLINE void Extension<PointerToArray<Element> >::
__setitem__(size_t n, const Element &value) {
  this->_this->set_element(n, value);
}

/**
 * This returns the entire contents of the vector as a block of raw data in a
 * string (or bytes object, in Python 3).
 *
 * @deprecated use memoryview(pta) or bytearray(pta) instead.
 */
template<class Element>
INLINE PyObject *Extension<PointerToArray<Element> >::
get_data() const {
  return PyBytes_FromStringAndSize((char *)this->_this->p(), sizeof(Element) * this->_this->size());
}

/**
 * This method exists mainly to access the data of the array easily from a
 * high-level language such as Python.
 *
 * This replaces the entire contents of the vector from a block of raw data
 * in a string (or bytes object, in Python 3).
 */
template<class Element>
INLINE void Extension<PointerToArray<Element> >::
set_data(PyObject *data) {
  if (PyObject_CheckBuffer(data)) {
    // User passed a buffer object.
    Py_buffer view;
    if (PyObject_GetBuffer(data, &view, PyBUF_CONTIG_RO) == -1) {
      PyErr_SetString(PyExc_TypeError,
                      "PointerToArray.set_data() requires a contiguous buffer");
      return;
    }

    if (view.itemsize != 1 && view.itemsize != sizeof(Element)) {
      PyErr_SetString(PyExc_TypeError,
                      "buffer.itemsize does not match PointerToArray element size");
      return;
    }

    if (view.len % sizeof(Element) != 0) {
      PyErr_Format(PyExc_ValueError,
                   "byte buffer is not a multiple of %zu bytes",
                   sizeof(Element));
      return;
    }

    if (view.len > 0) {
      this->_this->resize(view.len / sizeof(Element));
      memcpy(this->_this->p(), view.buf, view.len);
    } else {
      this->_this->clear();
    }

    PyBuffer_Release(&view);
    return;
  }

  Dtool_Raise_TypeError("PointerToArray.set_data() requires a buffer object");
}

/**
 * This returns the contents of a portion of the vector--from element (n)
 * through element (n + count - 1)--as a block of raw data in a string (or
 * bytes object, in Python 3).
 *
 * @deprecated use memoryview(pta) or bytearray(pta) instead.
 */
template<class Element>
INLINE PyObject *Extension<PointerToArray<Element> >::
get_subdata(size_t n, size_t count) const {
  n = (std::min)(n, this->_this->size());
  count = (std::max)(count, n);
  count = (std::min)(count, this->_this->size() - n);
  return PyBytes_FromStringAndSize((char *)(this->_this->p() + n), sizeof(Element) * count);
}

/**
 * Implements pickle support.
 */
template<class Element>
INLINE PyObject *Extension<PointerToArray<Element> >::
__reduce__(PyObject *self) const {
  // This preserves the distinction between a null vs. an empty PTA, though I'm
  // not sure that this distinction matters to anyone.
  if (this->_this->is_null()) {
    return Py_BuildValue("O()", Py_TYPE(self));
  }
  else if (this->_this->empty()) {
    return Py_BuildValue("O(())", Py_TYPE(self));
  }
  else {
    return Py_BuildValue("O(N)", Py_TYPE(self), get_data());
  }
}

/**
 * Same as get_element(), this returns the nth element of the array.
 */
template<class Element>
INLINE const Element &Extension<ConstPointerToArray<Element> >::
__getitem__(size_t n) const {
  return (*this->_this)[n];
}

/**
 * This returns the entire contents of the vector as a block of raw data in a
 * string (or bytes object, in Python 3).
 *
 * @deprecated use memoryview(pta) or bytearray(pta) instead.
 */
template<class Element>
INLINE PyObject *Extension<ConstPointerToArray<Element> >::
get_data() const {
  return PyBytes_FromStringAndSize((char *)this->_this->p(), sizeof(Element) * this->_this->size());
}

/**
 * This returns the contents of a portion of the vector--from element (n)
 * through element (n + count - 1)--as a block of raw data in a string (or
 * bytes object, in Python 3).
 *
 * @deprecated use memoryview(pta) or bytearray(pta) instead.
 */
template<class Element>
INLINE PyObject *Extension<ConstPointerToArray<Element> >::
get_subdata(size_t n, size_t count) const {
  n = (std::min)(n, this->_this->size());
  count = (std::max)(count, n);
  count = (std::min)(count, this->_this->size() - n);
  return PyBytes_FromStringAndSize((char *)(this->_this->p() + n), sizeof(Element) * count);
}

/**
 * Implements pickle support.
 */
template<class Element>
INLINE PyObject *Extension<ConstPointerToArray<Element> >::
__reduce__(PyObject *self) const {
  // This preserves the distinction between a null vs. an empty PTA, though I'm
  // not sure that this distinction matters to anyone.
  if (!this->_this->is_null() && this->_this->empty()) {
    return Py_BuildValue("O([])", Py_TYPE(self));
  }
  else {
    return Py_BuildValue("O(N)", Py_TYPE(self), get_data());
  }
}

/**
 * This is used to implement the buffer protocol, in order to allow efficient
 * access to the array data through a Python multiview object.
 */
template<class Element>
INLINE int Extension<PointerToArray<Element> >::
__getbuffer__(PyObject *self, Py_buffer *view, int flags) {
  const char *format = get_format_code(Element);
  if (format == nullptr) {
    // Not supported.
    return -1;
  }

  view->obj = Py_XNewRef(self);
  view->buf = (void*) this->_this->p();
  view->len = this->_this->size() * sizeof(Element);
  view->readonly = 0;
  view->itemsize = sizeof(Element);
  view->format = nullptr;
  if ((flags & PyBUF_FORMAT) == PyBUF_FORMAT) {
    view->format = (char*) format;
  }
  view->ndim = 1;
  view->shape = nullptr;
  if ((flags & PyBUF_ND) == PyBUF_ND) {
    // This leaks, which sucks, but __releasebuffer__ doesn't give us the same
    // pointer, so we would need to store it elsewhere if we wanted to delete
    // it there.  Eh, it's just an int, who cares.
    view->shape = new Py_ssize_t(this->_this->size());
  }
  view->strides = nullptr;
  if ((flags & PyBUF_STRIDES) == PyBUF_STRIDES) {
    view->strides = &(view->itemsize);
  }
  view->suboffsets = nullptr;

  // Store a reference to ourselves on the Py_buffer object as a reminder that
  // we have increased our refcount.
  this->_this->ref();
  view->internal = (void*) this->_this;

  return 0;
}

/**
 * This is used to implement the buffer protocol, in order to allow efficient
 * access to the array data through a Python memoryview object.
 */
template<>
INLINE int Extension<PointerToArray<LMatrix3f> >::
__getbuffer__(PyObject *self, Py_buffer *view, int flags) {
  view->obj = Py_XNewRef(self);
  view->buf = (void*) this->_this->p();
  set_matrix_view(*view, flags, this->_this->size(), 3, false, false);

  // Store a reference to ourselves on the Py_buffer object as a reminder that
  // we have increased our refcount.
  this->_this->ref();
  view->internal = (void*) this->_this;

  return 0;
}

/**
 * This is used to implement the buffer protocol, in order to allow efficient
 * access to the array data through a Python memoryview object.
 */
template<>
INLINE int Extension<PointerToArray<LMatrix3d> >::
__getbuffer__(PyObject *self, Py_buffer *view, int flags) {
  view->obj = Py_XNewRef(self);
  view->buf = (void*) this->_this->p();
  set_matrix_view(*view, flags, this->_this->size(), 3, true, false);

  // Store a reference to ourselves on the Py_buffer object as a reminder that
  // we have increased our refcount.
  this->_this->ref();
  view->internal = (void*) this->_this;

  return 0;
}

/**
 * This is used to implement the buffer protocol, in order to allow efficient
 * access to the array data through a Python memoryview object.
 */
template<>
INLINE int Extension<PointerToArray<UnalignedLMatrix4f> >::
__getbuffer__(PyObject *self, Py_buffer *view, int flags) {
  view->obj = Py_XNewRef(self);
  view->buf = (void*) this->_this->p();
  set_matrix_view(*view, flags, this->_this->size(), 4, false, false);

  // Store a reference to ourselves on the Py_buffer object as a reminder that
  // we have increased our refcount.
  this->_this->ref();
  view->internal = (void*) this->_this;

  return 0;
}

/**
 * This is used to implement the buffer protocol, in order to allow efficient
 * access to the array data through a Python memoryview object.
 */
template<>
INLINE int Extension<PointerToArray<UnalignedLMatrix4d> >::
__getbuffer__(PyObject *self, Py_buffer *view, int flags) {
  view->obj = Py_XNewRef(self);
  view->buf = (void*) this->_this->p();
  set_matrix_view(*view, flags, this->_this->size(), 4, true, false);

  // Store a reference to ourselves on the Py_buffer object as a reminder that
  // we have increased our refcount.
  this->_this->ref();
  view->internal = (void*) this->_this;

  return 0;
}

/**
 * Releases the buffer allocated by __getbuffer__.
 */
template<class Element>
INLINE void Extension<PointerToArray<Element> >::
__releasebuffer__(PyObject *self, Py_buffer *view) const {
  // Note: PyBuffer_Release automatically decrements view->obj.
  if (view->internal != nullptr) {
    // Oh, right, let's not forget to unref this.
    unref_delete((const PointerToArray<Element> *)view->internal);
    view->internal = nullptr;
  }
}

/**
 * A special Python method that is invoked by copy.deepcopy(pta).  This makes
 * sure that there is truly a unique copy of the array.
 */
template<class Element>
INLINE PointerToArray<Element> Extension<PointerToArray<Element> >::
__deepcopy__(PyObject *memo) const {
  PointerToArray<Element> copy;
  if (!this->_this->is_null()) {
    copy.v() = this->_this->v();
  }
  return copy;
}

/**
 * This is used to implement the buffer protocol, in order to allow efficient
 * access to the array data through a Python multiview object.
 */
template<class Element>
INLINE int Extension<ConstPointerToArray<Element> >::
__getbuffer__(PyObject *self, Py_buffer *view, int flags) const {
  if ((flags & PyBUF_WRITABLE) == PyBUF_WRITABLE) {
    PyErr_SetString(PyExc_BufferError,
                    "Object is not writable.");
    return -1;
  }

  const char *format = get_format_code(Element);
  if (format == nullptr) {
    // Not supported.
    return -1;
  }

  view->obj = Py_XNewRef(self);
  view->buf = (void*) this->_this->p();
  view->len = this->_this->size() * sizeof(Element);
  view->readonly = 1;
  view->itemsize = sizeof(Element);
  view->format = nullptr;
  if ((flags & PyBUF_FORMAT) == PyBUF_FORMAT) {
    view->format = (char*) format;
  }
  view->ndim = 1;
  view->shape = nullptr;
  if ((flags & PyBUF_ND) == PyBUF_ND) {
    // This leaks, which sucks, but __releasebuffer__ doesn't give us the same
    // pointer, so we would need to store it elsewhere if we wanted to delete
    // it there.  Eh, it's just an int, who cares.
    view->shape = new Py_ssize_t(this->_this->size());
  }
  view->strides = nullptr;
  if ((flags & PyBUF_STRIDES) == PyBUF_STRIDES) {
    view->strides = &(view->itemsize);
  }
  view->suboffsets = nullptr;

  // Store a reference to ourselves on the Py_buffer object as a reminder that
  // we have increased our refcount.
  this->_this->ref();
  view->internal = (void*) this->_this;

  return 0;
}

/**
 * Specialization on __getbuffer__ for LMatrix3f.
 */
template<>
INLINE int Extension<ConstPointerToArray<LMatrix3f> >::
__getbuffer__(PyObject *self, Py_buffer *view, int flags) const {
  if ((flags & PyBUF_WRITABLE) == PyBUF_WRITABLE) {
    PyErr_SetString(PyExc_BufferError,
                    "Object is not writable.");
    return -1;
  }
  view->obj = Py_XNewRef(self);
  view->buf = (void*) this->_this->p();
  set_matrix_view(*view, flags, this->_this->size(), 3, false, true);

  // Store a reference to ourselves on the Py_buffer object as a reminder that
  // we have increased our refcount.
  this->_this->ref();
  view->internal = (void*) this->_this;

  return 0;
}

/**
 * Specialization on __getbuffer__ for LMatrix3d.
 */
template<>
INLINE int Extension<ConstPointerToArray<LMatrix3d> >::
__getbuffer__(PyObject *self, Py_buffer *view, int flags) const {
  if ((flags & PyBUF_WRITABLE) == PyBUF_WRITABLE) {
    PyErr_SetString(PyExc_BufferError,
                    "Object is not writable.");
    return -1;
  }
  view->obj = Py_XNewRef(self);
  view->buf = (void*) this->_this->p();
  set_matrix_view(*view, flags, this->_this->size(), 3, true, true);

  // Store a reference to ourselves on the Py_buffer object as a reminder that
  // we have increased our refcount.
  this->_this->ref();
  view->internal = (void*) this->_this;

  return 0;
}

/**
 * Specialization on __getbuffer__ for UnalignedLMatrix4f.
 */
template<>
INLINE int Extension<ConstPointerToArray<UnalignedLMatrix4f> >::
__getbuffer__(PyObject *self, Py_buffer *view, int flags) const {
  if ((flags & PyBUF_WRITABLE) == PyBUF_WRITABLE) {
    PyErr_SetString(PyExc_BufferError,
                    "Object is not writable.");
    return -1;
  }
  view->obj = Py_XNewRef(self);
  view->buf = (void*) this->_this->p();
  set_matrix_view(*view, flags, this->_this->size(), 4, false, true);

  // Store a reference to ourselves on the Py_buffer object as a reminder that
  // we have increased our refcount.
  this->_this->ref();
  view->internal = (void*) this->_this;

  return 0;
}

/**
 * Specialization on __getbuffer__ for UnalignedLMatrix4d.
 */
template<>
INLINE int Extension<ConstPointerToArray<UnalignedLMatrix4d> >::
__getbuffer__(PyObject *self, Py_buffer *view, int flags) const {
  if ((flags & PyBUF_WRITABLE) == PyBUF_WRITABLE) {
    PyErr_SetString(PyExc_BufferError,
                    "Object is not writable.");
    return -1;
  }
  view->obj = Py_XNewRef(self);
  view->buf = (void*) this->_this->p();
  set_matrix_view(*view, flags, this->_this->size(), 4, true, true);

  // Store a reference to ourselves on the Py_buffer object as a reminder that
  // we have increased our refcount.
  this->_this->ref();
  view->internal = (void*) this->_this;

  return 0;
}

/**
 * Releases the buffer allocated by __getbuffer__.
 */
template<class Element>
INLINE void Extension<ConstPointerToArray<Element> >::
__releasebuffer__(PyObject *self, Py_buffer *view) const {
  // Note: PyBuffer_Release automatically decrements obj->view.
  if (view->internal != nullptr) {
    // Oh, right, let's not forget to unref this.
    unref_delete((const PointerToArray<Element> *)view->internal);
    view->internal = nullptr;
  }
}

/**
 * A special Python method that is invoked by copy.deepcopy(pta).  This makes
 * sure that there is truly a unique copy of the array.
 */
template<class Element>
INLINE ConstPointerToArray<Element> Extension<ConstPointerToArray<Element> >::
__deepcopy__(PyObject *memo) const {
  PointerToArray<Element> copy;
  if (!this->_this->is_null()) {
    copy.v() = this->_this->v();
  }
  return copy;
}
